package joodle.si;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.swing.Icon;

import joodle.domain.Ellipse;
import joodle.domain.Line;
import joodle.domain.Rectangle;
import joodle.domain.Shape;
import joodle.domain.ShapeFactory;
import joodle.domain.Text;

/**
 * This is a utility class with methods for loading addons and returning factory objects from 
 * discovered valid addons and built-in shapes.
 * <p><center>
 * <img src="http://ug.bcc.bilkent.edu.tr/~m_ozdal/Site/Reports_files/design_files/image013.jpg" />
 * </center></p>
 */
public class AddonManager {
	
	// By defaults add-ons are in the current directory (each
	// in a separate jar file)
	private static String addonsDir = ".";

	private AddonManager() {}
	
	/**
	 * Sets the add-ons' directory to the given new directory
	 * @param dirName name of the new directory
	 */
	public static void setAddonsDir(String dirName) {
		AddonManager.addonsDir = dirName;
	}
	
	/**
	 * This method returns an array of shape factories that can be used to make new shapes. It makes 
	 * the factories for the built-in shapes and then looks for add-on shapes in the add-on directory 
	 * and adds their factories to the factories list.
	 */
	public static ShapeFactory[] factories() {
		// add built-in shapes
		List<ShapeFactory> l = new ArrayList<ShapeFactory>();

		l.add(new ShapeFactory() {
			public Shape makeShape() { return new Line(); }
			public String shapeName() { return "Line"; }
			public Icon shapeIcon() { return ResourceManager.makeIcon("line"); }
		});
		l.add(new ShapeFactory() {
			public Shape makeShape() { return new Rectangle(); }
			public String shapeName() { return "Rectangle"; }
			public Icon shapeIcon() { return ResourceManager.makeIcon("rectangle"); }
		});
		l.add(new ShapeFactory() {
			public Shape makeShape() { return new Ellipse(); }
			public String shapeName() { return "Ellipse"; }
			public Icon shapeIcon() { return ResourceManager.makeIcon("ellipse"); }
		});
		l.add(new ShapeFactory() {
			public Shape makeShape() { return new Text(); }
			public String shapeName() { return "Text"; }
			public Icon shapeIcon() { return ResourceManager.makeIcon("text"); }
		});
		
		// find add-ons
		final File d = new File(addonsDir);
		if(d.exists()) {
			File[] fs = d.listFiles(new FilenameFilter() {
				
				@Override
				public boolean accept(File f, String n) {
					return f.equals(d) && n.endsWith(".jar");
				}
				
			});
			
			for(final File jf : fs) {
				try {
					JarFile zf = new JarFile(jf);
					Enumeration<JarEntry> jes = zf.entries();
					
					while(jes.hasMoreElements()) {
						JarEntry je = jes.nextElement();
						final String className = je.getName().replaceAll(".*/", "").replaceAll(".class", "");
						final URL u = new URL("jar:file:" + jf.getCanonicalPath() + "!/");
						final Shape s = makeInstance(u, className);
						if( s != null) { // if it can be instantiated

							l.add(new ShapeFactory() {
								public Shape makeShape() { return makeInstance(u, className); }
								public String shapeName() { return className; }
								public Icon shapeIcon() { return ResourceManager.makeIcon(s.getClass()); }

							});
						}
					}
				}
				catch(IOException ioe) { ioe.printStackTrace();/* ignore errors */ }
			}
		}
			
		return l.toArray(new ShapeFactory[0]);
	}

	/**
	 * Tries to instantiate a Shape from the class with name cn and from the URL given by u 
	 * @param u URL for the class path
	 * @param cn Class name
	 * @return an instance of Shape if class implements it, null otherwise
	 */
	private static Shape makeInstance(URL u, String cn) {
		Class<?> c = null;
		try {
			ClassLoader cl = URLClassLoader.newInstance(new URL[] { u });
			c = cl.loadClass(cn);
		}
		catch(ClassNotFoundException e) { return null; }
		
		try {
			return (Shape) c.newInstance();
		}
		catch(IllegalAccessException e) { return null; }
		catch(InstantiationException e) { return null; }
	}
}
